OpenAI Realtime 세션 시작 실패 원인 분석

분석 대상 로그: 6-019e3a4e-3cb5-7921-a41f-f00ff27f1335_0_467785f4-4286-4567-b0d3-93e687024a8c.json

주요 에러 시각: 2026-05-18T09:20:36.242Z

결론

The string did not match the expected pattern.는 OpenAI Realtime API가 직접 반환한 에러 메시지라기보다, 브라우저 WebRTC 엔진이 OpenAI Realtime 서버에서 받은 SDP answer 문자열을 RTCPeerConnection.setRemoteDescription()에서 파싱하다 실패한 런타임 에러일 가능성이 가장 높다.

현재 로그만으로는 instruction 문자열 문제라고 판단할 근거가 낮다. 로그에는 activity instruction 본문, OpenAI API 400/500 응답, Call creation failed, Invalid SDP response 같은 서버/API 실패 흔적이 없다.

전체 로그 흐름

시간 레벨 로그 해석
08:58:57 - 09:02:25 INFO Entry request failed, continuing in standalone mode
RoomNotFound
게스트 방 입장 요청이 실패했지만, 코드상 standalone mode로 계속 진행한다. 직접적인 AI 세션 실패 원인은 아니다.
09:08:04 - 09:19:42 WARN setSinkId failed: DOMException: A user gesture is required 사용자 제스처 없이 오디오 출력 장치를 지정하려다 브라우저가 거부했다. catch 후 경고만 남기므로 세션 시작을 중단시키지는 않는다.
09:20:36.242 ERROR [AI_SESSION] Failed to start session
The string did not match the expected pattern.
activityId: activity-1779091460856-4
AI 세션 시작 중 실제 실패가 발생했다. activityId는 실패한 activity 식별자이며, 그 자체가 instruction 문제를 의미하지 않는다.
09:20:36.243 ERROR [AI_SESSION] Failed to start session (handler) 내부 startSession 에러가 handler에서 재로깅된 것이다. 원인은 직전 에러와 동일하다.

관련 코드 경로

세션 시작 흐름은 다음과 같다.

const offer = await pc.createOffer();
await pc.setLocalDescription(offer);

const answer = await createSession(
  offer.sdp,
  activity,
  voice || avatar?.voice || DEFAULT_VOICE,
  user,
  avatar,
  subAvatars,
  realtimeModelVersion?.current,
  signal,
  turnDetectionRef.current.silence_duration_ms,
);

await pc.setRemoteDescription(answer);

answer.sdp는 우리 서버가 생성한 값이 아니라, 우리 서버가 OpenAI Realtime API https://api.openai.com/v1/realtime/calls에 offer SDP를 전달한 뒤 text로 받은 OpenAI 서버의 SDP answer다.

formData.set("sdp", sdp);
formData.set("session", JSON.stringify(sessionConfig));

const res = await fetch("https://api.openai.com/v1/realtime/calls", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
  },
  body: formData,
});

const answerSdp = await res.text();

instruction 문제 가능성

낮음.

instruction 문제가 직접 원인이라면 보통 다음과 같은 흔적이 있어야 한다.

제공된 로그에는 위 흔적이 없다. 또한 instruction 치환 코드는 정규식 기반 문자열 처리이며, 해당 메시지를 직접 생성하는 경로가 확인되지 않았다.

setSinkId 로그와의 관계

직접 관련은 낮음.

DOMException: A user gesture is requiredaudioEl.setSinkId(audioOutputDeviceId)에서 발생한다. 이 호출은 try/catch로 감싸져 있고 실패해도 경고만 남긴 뒤 세션 시작 흐름은 계속 진행된다.

try {
  await audioEl.setSinkId(audioOutputDeviceId);
} catch (e) {
  console.warn("setSinkId failed:", e);
}

따라서 이 경고는 원하는 출력 장치로 소리가 나지 않을 수 있다는 신호이지, The string did not match the expected pattern. 세션 실패의 직접 원인으로 보기는 어렵다.

네트워크 문제 가능성

직접 가능성은 낮고, 간접 가능성은 제한적으로 있음.

일반적인 네트워크 문제라면 보통 다음 에러가 나온다.

현재 에러는 이미 받은 문자열을 브라우저가 특정 패턴으로 파싱하다 실패하는 형태다. 다만 프록시/서버 문제로 SDP 응답이 변형되거나 잘못 전달된다면 간접적으로 비슷한 현상이 날 수 있다.

가장 가능성 높은 원인

  1. OpenAI Realtime SDP answer와 현재 브라우저 WebRTC 파서의 호환성 문제
    특히 Safari/WebKit 계열이면 SDP 파싱이 더 엄격할 수 있다.
  2. SDP 문자열 형식 문제
    줄바꿈, 지원하지 않는 SDP attribute, codec/header extension 등으로 setRemoteDescription()에서 실패했을 수 있다.
  3. 응답 객체 검증 부족
    answer.type, answer.sdp, SDP 앞부분/길이를 실패 직전에 기록하지 않아 현재 로그만으로 확정이 어렵다.

권장 대처

1. 실패 지점 로그 보강

try {
  await pc.setRemoteDescription(answer);
} catch (error) {
  logger.error("Failed to apply realtime answer", {
    errorName: error instanceof Error ? error.name : undefined,
    errorMessage: error instanceof Error ? error.message : String(error),
    errorStack: error instanceof Error ? error.stack : undefined,
    answerType: answer?.type,
    sdpStartsWithV: answer?.sdp?.startsWith("v="),
    sdpLength: answer?.sdp?.length,
    sdpHead: answer?.sdp?.slice(0, 300),
    activityId: activity.id,
  });
  throw error;
}

2. setRemoteDescription 전 answer 검증

if (
  answer?.type !== "answer" ||
  typeof answer.sdp !== "string" ||
  !answer.sdp.startsWith("v=")
) {
  throw new Error("Invalid realtime answer SDP");
}

3. SDP 줄바꿈 정규화

const normalizedAnswer = {
  type: "answer" as const,
  sdp: answer.sdp.replace(/\r?\n/g, "\r\n"),
};

await pc.setRemoteDescription(normalizedAnswer);

4. 1회 재시도

setRemoteDescription 실패 시 기존 RTCPeerConnection을 닫고, offer 생성부터 OpenAI Realtime call 생성까지 한 번만 재시도한다. 같은 SDP answer를 재사용하지 않는 것이 중요하다.

5. Safari/WebKit 대응

추가 확인에 필요한 로그

최종 판단

현재 로그 전체 흐름에서는 AI 세션 시작 중 WebRTC remote SDP 적용 실패가 가장 타당한 원인이다. instruction 문자열 문제, 일반 네트워크 문제, setSinkId 사용자 제스처 문제는 직접 원인으로 보기 어렵다.

다음 재현 시에는 SDP answer와 error metadata를 남기도록 로그를 보강해야 원인을 확정할 수 있다.